package org.osmdroid.views.overlay;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.PorterDuff.Mode;
import android.os.Build;
import android.util.Log;

import org.osmdroid.api.IMapView;
import org.osmdroid.views.MapView;

import timber.log.Timber;

/**
 * This will allow an {@link Overlay} that is not HW acceleration compatible to work in a HW
 * accelerated MapView. It will create a screen-sized backing Bitmap for the Overlay to draw to and
 * then will draw the Bitmap to the HW accelerated canvas. Due to the extra work, it does not draw
 * the shadow layer. If the Canvas passed into the Overlay is not HW accelerated or if
 * {@link #isUsingBackingBitmap()} returns false then it draws normally (including the shadow layer)
 * without the backing Bitmap. <br>
 * <br>
 * TODO:
 * <ol>
 * <li>Implement a flag to determine if the drawing has actually changed. If not, then reuse the
 * last frame's backing bitmap. This will prevent having to re-upload the bitmap texture to GPU.</li>
 * </ol>
 */
public abstract class NonAcceleratedOverlay extends Overlay {

    private Bitmap mBackingBitmap;
    private Canvas mBackingCanvas;
    private final Matrix mBackingMatrix = new Matrix();
    private final Matrix mCanvasIdentityMatrix = new Matrix();

    /**
     * A delegate for {@link #draw(Canvas, MapView, boolean)}.
     */
    protected abstract void onDraw(Canvas c, MapView osmv, boolean shadow);

    /**
     * Use {@link #NonAcceleratedOverlay()} instead
     */
    @Deprecated
    public NonAcceleratedOverlay(Context ctx) {
        super(ctx);
    }

    public NonAcceleratedOverlay() {
        super();
    }


    /**
     * Override if you really want access to the original (possibly) accelerated canvas.
     */
    protected void onDraw(Canvas c, Canvas acceleratedCanvas, MapView osmv, boolean shadow) {
        onDraw(c, osmv, shadow);
    }

    /**
     * Allow forcing this overlay to skip drawing using backing Bitmap by returning false.
     */
    public boolean isUsingBackingBitmap() {
        return true;
    }

    @Override
    public void onDetach(MapView mapView) {
        mBackingBitmap = null;
        mBackingCanvas = null;
        super.onDetach(mapView);
    }

    @Override
    public final void draw(Canvas c, MapView osmv, boolean shadow) {
        // First check to see if we want to use the backing bitmap
        final boolean atLeastHoneycomb = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
        if (isUsingBackingBitmap() && atLeastHoneycomb && c.isHardwareAccelerated()) {
            // Drawing a shadow layer would require a second backing Bitmap due to the way HW
            // accelerated drawBitmap works. One could extend this Overlay to implement that if
            // needed.
            if (shadow)
                return;

            // If we don't have any drawing area, then don't draw
            if (c.getWidth() == 0 || c.getHeight() == 0)
                return;

            if (mBackingBitmap == null || mBackingBitmap.getWidth() != c.getWidth()
                    || mBackingBitmap.getHeight() != c.getHeight()) {
                mBackingBitmap = null;
                mBackingCanvas = null;
                try {
                    mBackingBitmap = Bitmap.createBitmap(c.getWidth(), c.getHeight(),
                            Config.ARGB_8888);
                } catch (OutOfMemoryError e) {
                    Timber.e( "OutOfMemoryError creating backing bitmap in NonAcceleratedOverlay.");
                    System.gc();
                    return;
                }

                mBackingCanvas = new Canvas(mBackingBitmap);
            }

            mBackingCanvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
            c.getMatrix(mBackingMatrix);
            mBackingCanvas.setMatrix(mBackingMatrix);
            onDraw(mBackingCanvas, c, osmv, shadow);
            c.save();
            c.getMatrix(mCanvasIdentityMatrix);
            mCanvasIdentityMatrix.invert(mCanvasIdentityMatrix);
            c.concat(mCanvasIdentityMatrix);
            c.drawBitmap(mBackingBitmap, 0, 0, null);
            c.restore();
        } else
            onDraw(c, c, osmv, shadow);
    }
}
